package com.eltonkent.dashboard.util.gps;

import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import android.content.Context;
import android.content.SharedPreferences;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;

import com.eltonkent.dashboard.R;
import com.eltonkent.dashboard.constants.ApplicationConstants;
import com.eltonkent.dashboard.data.container.GeoCodeContainer;
import com.eltonkent.dashboard.data.container.WeatherContainer;
import com.eltonkent.dashboard.service.ServiceManagerImpl;
import com.eltonkent.dashboard.service.helper.BearerHelper;
import com.eltonkent.dashboard.service.requesthandler.GoogleReverseCodeRequest;
import com.eltonkent.dashboard.service.requesthandler.GoogleWeatherRequest;
import com.eltonkent.dashboard.service.requesthandler.HttpRequest;
import com.eltonkent.dashboard.service.requesthandler.YahooReverseCodeRequest;
import com.eltonkent.dashboard.service.requesthandler.YahooWeatherRequest;
import com.eltonkent.dashboard.util.logger.Logger;

/**
 * Takes care of updating the dashboard activty of all location based services
 * -Geocoding -
 * 
 * @author elton.stephen.kent
 * 
 */
public class DashboardLocationManager {
	private final class LocationService extends Thread implements
			WebResponseNotificationListener {
		private double serviceLatitude;
		private double serviceLongitude;
		private boolean useNetwork;
		private boolean canRunLocationService = true;

		private GeoCodeContainer addressContainer;

		private boolean hasTemperatureInfo;

		private LocationService(boolean useNetwork) {
			Logger.i(TAG, "Starting geocoding daemon. useNetwork->"
					+ useNetwork);
			this.useNetwork = useNetwork;
		}

		private void addGeoTagRequest() {
			if (currentLatitude != serviceLatitude
					&& currentLongitude != serviceLongitude) {
				Logger.d(TAG, "Reverse Geocode request submitted");
				/* send reverse geocoding request */
				GoogleReverseCodeRequest reversecode = new GoogleReverseCodeRequest(
						this, currentLatitude, currentLongitude, false);
				addHttpRequest(reversecode);
				serviceLatitude = currentLatitude;
				serviceLongitude = currentLongitude;
			}
		}

		private void addHttpRequest(HttpRequest httpRequest) {
			ServiceManagerImpl.getInstance().submit(httpRequest);
		}

		private void addTemperatureRequest() {
			if (addressContainer != null) {
				HttpRequest httpRequest = null;
				if (addressContainer.isGoogleResponse()) {
					// add yahoo woeid request
					httpRequest = new YahooReverseCodeRequest(this,
							addressContainer.getLatitude(), addressContainer
									.getLongitude(), true);

				} else {
					// add yahoo weather request
					httpRequest = new YahooWeatherRequest(this,
							addressContainer.getWoeid());

				}
				addHttpRequest(httpRequest);
				hasTemperatureInfo = true;
			}
		}

		@Override
		public void notifyAddress(GeoCodeContainer deser) {
			this.addressContainer = deser;
			if (deser.getLocationAddress() != null) {
				locationCallback.setLocationAddress(deser.getLocationAddress(),
						true);
				GoogleWeatherRequest googlewet=new GoogleWeatherRequest(this, deser.getLocationAddress());
				addHttpRequest(googlewet);
				if (!hasTemperatureInfo) {
					addTemperatureRequest();
				}
				return;
			}
			locationCallback.setLocationAddress(context.getResources()
					.getString(R.string.addresserror), false);

		}

		@Override
		public void notifyWeather(WeatherContainer weather) {
			locationCallback.setWeatherInfo(weather);
		}

		@Override
		public void run() {

			Logger.e(TAG, "Started location service");
			for (; canRunLocationService;) {
				Logger.d(TAG, this.getName() + " is running");
				try {
					sleep(UPDATE_DELAY);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (useNetwork
						&& BearerHelper.getInstance(context).getCurrentBearer() != null) {
					/* Add reverse coding request */
					addGeoTagRequest();
					/* Add temperature request */
					if (!hasTemperatureInfo) {
						addTemperatureRequest();
					}
				} else {
					if (currentLatitude != 0 && currentLongitude != 0) {
						String geocoderString = "Lat=" + currentLatitude
								+ "  Long=" + currentLongitude;
						locationCallback.setLocationAddress(geocoderString,
								true);
					}
				}
			}
			Logger.e(TAG, " Location Daemon stopped");
		}

		private void setCanUseNetwork(boolean canUseNetwork) {
			useNetwork = canUseNetwork;
		}

		private void stopService() {
			canRunLocationService = false;
		}

		@Override
		public String getLastKnownAddress() {
			if(addressContainer!=null){
			if(addressContainer.getLocationAddress()!=null)
				return addressContainer.getLocationAddress();
			}
			return null;
		}

	}

	private static final long LOCATION_TIMEOUT = 60000L;
	private static final int UPDATE_DELAY = 5000;
	private static final String TAG = "DBA_LOCATION_MANAGER";
	private Context context;
	private ILocationListener locationCallback;
	private List<String> arProvider;
	private LocationManager locationManager;
	private String bestProvider;
	private boolean bSuccess = false;
	private TimerTask ttask = null;

	private LocationService geoCodingService;

	private boolean isRunning;

	private double currentLatitude;

	private double currentLongitude;

	private LocationListener mListener = new LocationListener() {

		private double oldLatitude;
		private double oldLongitude;
		private double oldAltitude;
		private double currentAltitude;

		@Override
		public void onLocationChanged(Location location) {
			bSuccess = true;
			currentLatitude = location.getLatitude();
			currentLongitude = location.getLongitude();
			currentAltitude = location.getAltitude();
			/* make sure the same alititude is not send */
			if (currentAltitude != 0) {
				if (currentAltitude != oldAltitude) {
					oldAltitude = location.getAltitude();
					locationCallback.setAltitude(currentAltitude);
				}
			}
			/* make sure the lat and long is non zero */
			if (oldLatitude == 0 || oldLongitude == 0) {
				/* the first coordinates */
				Logger.d(TAG, "The first coordinate is received!" + location);
				oldLatitude = currentLatitude;
				oldLongitude = currentLongitude;
				locationCallback.movementInitiated();
				geoCodingService.addGeoTagRequest();
			}
			/* Calculate the distances */
			locationCallback.setLocation(location);
		}

		@Override
		public void onProviderDisabled(String provider) {
			locationManager.removeUpdates(this);
			locationCallback.locationProviderDisabled();
		}

		@Override
		public void onProviderEnabled(String provider) {

		}

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
			// TODO Auto-generated method stub

		}

	};

	public DashboardLocationManager(Context context, ILocationListener callback) {
		this.context = context;
		this.locationCallback = callback;
		locationManager = (LocationManager) context.getApplicationContext()
				.getSystemService(Context.LOCATION_SERVICE);
	}

	private void doLocationSearchTimeout() {
		if (!bSuccess) {
			shutdownLocationManager();
			Looper.prepare();
			locationCallback.locationSearchTimeout();
			Looper.loop();
		}
		if (ttask != null) {
			ttask.cancel();
			ttask = null;
		}
	}

	/**
	 * Get the available providers from the device
	 */
	private void getAvailableProviders() {

		Criteria crit = new Criteria();

		crit.setAccuracy(Criteria.NO_REQUIREMENT);

		crit.setPowerRequirement(Criteria.NO_REQUIREMENT);

		crit.setAltitudeRequired(false);
		crit.setBearingRequired(false);
		crit.setSpeedRequired(false);

		bestProvider = locationManager.getBestProvider(crit, true);
		if (bestProvider != null
				&& bestProvider.equals(LocationManager.GPS_PROVIDER)) {
			crit.setAltitudeRequired(true);
		}
		arProvider = locationManager.getAllProviders();
	}

	/**
	 * Check if any provider is available
	 * 
	 * @return
	 */
	public boolean hasProvider() {
		getAvailableProviders();
		if (arProvider == null || arProvider.isEmpty()) {
			return false;
		}
		int nRunningProvider = 0;
		for (String ProviderTemp : arProvider) {
			if (locationManager.isProviderEnabled(ProviderTemp)) {
				Logger.w(TAG, ">>> checkProvider : " + ProviderTemp);
				if (ProviderTemp.equals(LocationManager.PASSIVE_PROVIDER) == true) {
					Logger.w(TAG, ">>> passive provider skip.");
					continue;
				}
				nRunningProvider++;
			}
		}
		bSuccess = false;
		if (nRunningProvider == 0) {
			return false;
		}
		return true;
	}

	public boolean isRunning() {
		return isRunning;
	}

	public void shutdownLocationManager() {
		isRunning = false;
		locationManager.removeUpdates(mListener);
		if (geoCodingService != null) {
			geoCodingService.stopService();
			geoCodingService = null;
		}
	}

	public boolean startGetLocation() {
		if (arProvider == null || arProvider.isEmpty()) {
			return false;
		}
		/**
		 * Start the geo tagging service
		 */
		SharedPreferences prefs = context.getSharedPreferences(
				ApplicationConstants.PREFERENCE_FILE, Context.MODE_PRIVATE);
		boolean useNetworks = prefs.getBoolean(context.getResources()
				.getString(R.string.key_enable_data), true);
		geoCodingService = new LocationService(useNetworks);
		geoCodingService.setName("DBA_Location-Service-Thread"
				+ new Date(System.currentTimeMillis()));
		geoCodingService.start();

		/**
		 * Request location updates
		 */
		int nRunningProvider = 0;

		for (String ProviderTemp : arProvider) {
			if (locationManager.isProviderEnabled(ProviderTemp)) {
				Logger.w(TAG, ">>> requestLocationUpdates : " + ProviderTemp);
				if (ProviderTemp.equals(LocationManager.PASSIVE_PROVIDER) == true) {
					Logger.w(TAG, ">>> passive provider skip.");
					continue;
				}
				locationManager.requestLocationUpdates(ProviderTemp, 0, 0,
						mListener);
				isRunning = true;
				nRunningProvider++;
			}
		}

		if (nRunningProvider == 0) {// requestLocationUpdates count-> 0
			return false;
		}
		if (ttask != null) {
			ttask.cancel();
			ttask = null;
		}
		if (ttask == null) {
			ttask = new TimerTask() {

				@Override
				public void run() {
					doLocationSearchTimeout();
				}
			};
			new Timer().schedule(ttask, LOCATION_TIMEOUT);
		}
		return true;
	}

	public void turnOffDataServices(boolean turnOff) {
		Logger.d(TAG, "Turning off data services=>" + turnOff);
		if (geoCodingService != null) {
			geoCodingService.setCanUseNetwork(turnOff);
		}
	}

}
